﻿#include <iostream>
using namespace std;

#if 0
 /*对象拷贝时的编译器优化
• 现代编译器会为了尽可能提⾼程序的效率，在不影响正确性的情况下会尽可能减少⼀些传参和传参过程中可以省略的拷贝*/

class Date
{
public:

	// 默认构造函数
	Date(int year = 1949, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "默认构造函数" << endl;
	}

	// 析构函数
	~Date()
	{
		//...
		cout << "析构函数" << endl;
	}

	// 拷贝构造
	Date(const Date& d)
	{
		cout << "拷贝构造" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	// 赋值运算符重载
	Date& operator=(const Date& d)
	{
		cout << "赋值运算符重载" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

	void Print() const  // const Date* const this
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 1949;
	int _month = 1;
	int _day = 1;
};

void fun1(Date d)
{}

Date fun2()
{
	Date d;
	return d;
}

int main()
{
	// 传值传参
	// 由于fun1的参数不是Date&,会形成一次拷贝构造
	// 这边打印的应该是(d1的构造函数)+(d1传参的拷贝构造)
	// 然后是析构函数*2
	//Date d1;
	//fun1(d1);

	// 如果是隐式类型转换，连续的构造+拷贝构造->编译器优化成直接构造
	// 然后是析构函数
	//fun1({ 2024, 1, 1 });

	// ⼀个表达式中，连续的构造+拷贝构造->优化为⼀个构造
	//fun1(Date(2024, 1, 1));

	// 传值返回
	// 函数表达式中先构造d，然后在构造一个临时对象拷贝构造d进行返回
	// 但是在一些编译器上会直接优化一个拷贝构造,减少一个拷贝
	// ⼀些编译器会优化得更厉害，进⾏跨⾏合并优化，直接变为构造。
	//fun2();

	// 如果我们又去接受返回值
	// 直接构造d，构造临时对象进行拷贝构造d，
	// 返回接收的dd又进行一次直接构造+拷贝构造。
	//返回时⼀个表达式中，连续直接构造+拷贝构造->优化⼀个拷⻉构造
	//⼀些编译器会优化得更厉害，进⾏跨⾏合并优化，直接变为构造。
	//Date dd = fun2();

	// ⼀个表达式中，连续拷贝构造+赋值重载->⽆法优化
	Date dd;
	dd = fun2();

	return 0;
}


// C/C++内存管理
// 我们知道在内存区大致分为以下几块：栈区，堆区，静态区(数据段)，常量区(代码段)

// 我们来分析以下代码，看看他们分别处于什么区域
/*	globalVar在哪里？____
	staticGlobalVar在哪里？____
	staticVar在哪里？____
	localVar在哪里？____
	num1 在哪里？____
	char2在哪里？____
	*char2在哪里？___
	pChar3在哪里？____
	*pChar3在哪里？____
	ptr1在哪里？____
	*ptr1在哪里？____		*/

// globalVar是全局变量，属于静态区(数据段)
int globalVar = 1;

// staticGlobalVar是静态变量，属于静态区(数据段)
static int staticGlobalVar = 1;

void Test()
{
	// staticVar是Test函数栈帧空间上的静态变量，属于静态区(数据段)
	static int staticVar = 1;

	// localVar是正常变量，在栈上
	int localVar = 1;

	// num1是数组的名字，数组名在此处代表首元素地址在栈上
	int num1[10] = { 1, 2, 3, 4 };

	// char2是数组的名字，数组名在此处代表首元素地址在栈上
	// *char2表示首元素'a'，处于栈上
	char char2[] = "abcd";

	// const修饰char* pChar3
	// pChar3在栈上
	// *pChar3由于const的修饰在常量区(代码段)
	const char* pChar3 = "abcd";

	// 以下的ptr(1~3)属于一个名称，在栈上
	// *ptr(1~3)访问的是申请的空间在堆区
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);

	free(ptr1);
	free(ptr3);
}



// C语言中动态内存管理方式：malloc / calloc / realloc / free

void Test()
{
	// 1.malloc/calloc/realloc的区别是什么？
	// 三者都是可以开空间
	// malloc:开空间不初始化
	// calloc:开空间可以初始化
	// realloc:可以在原有的空间上进行增容扩容
	int* p1 = (int*)calloc(4, sizeof(int));
	int* p2 = (int*)realloc(p1, sizeof(int) * 10);
	// 这里需要free(p2)吗？
	//free(p2);
	// 不需要，如果这块空间足够，realloc会直接向后开辟申请空间，不需要释放空间
	// 如果这块空间不够，realloc会申请另一块新的空间，将原空间内部资源拷贝过去然后释放，
	// 不需要我们手动释放空间
} 

// c++中申请内存使用new/delete
// 不同于c语言中的malloc/realloc/free等等，new/delete使用会去调用构造函数和析构函数

class A
{
public:
	// 构造
	A(int a1 = 10, int a2 = 20)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1 = 10, int a2 = 20)" << endl;
	}

	// 拷贝构造
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
		_a1 = a._a1;
		_a2 = a._a2;
	}

	// 析构
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	// 声明成员变量，给缺省值用于初始化列表
	int _a1 = 10;
	int _a2 = 20;
};

int main()
{
	// new申请一个对象
	A* p1 = new A;
	delete p1;

	// new申请多个对象
	A* p2 = new A[4];
	delete[] p2;

	// new申请对象+初始化
	// 方法一：
	// 构造+拷贝
	A aa1(1, 1);
	A aa2(2, 2);
	A aa3(3, 3);
	A aa4(4, 4);
	A* p3 = new A[4]{aa1, aa2, aa3, aa4};
	delete[] p3;

	// 方法二：类型转换
	// 优化后：构造
	A* p4 = new A[4]{ {1,1},{2,2},{3,3},{4,4} };
	delete[] p4;

	// 方法三：匿名对象
	// 构造
	A* p5 = new A[4]{ A(1,1), A(2,2), A(3,3), A(4,4) };
	delete[] p5;

	return 0;
}

// 使用new来创建一个链表
class ListNode
{
public:
	ListNode(int val = 0)
		:_val(val)
		,_next(nullptr)
	{}

	int _val = 0;
	ListNode* _next;
};

int main()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);
	n1->_next = n2;
	n2->_next = n3;
	n3->_next = n4;

	delete n1;
	delete n2;
	delete n3;
	delete n4;

	return 0;
}

// new申请失败不会返回NULL,而是抛异常

void Text01()
{
	// 关键字 throw/try/catch
	void* p1 = new char[1024 * 1024 * 1024];  // 1G

	cout << p1 << endl;
	void* p2 = new char[1024 * 1024 * 1024];  // 1G
	cout << p2 << endl;

	void* p3 = new char[1024 * 1024 * 1024];  // 1G
	cout << p3 << endl;

}

void Text02()
{
	int n = 0;
	while (1)
	{
		void* p1 = new char[1024 * 1024];  // 1M
		++n;
		cout << p1 << "->" << n << endl;
	}
	
}
int main()
{
	try
	{
		// Text01();
		// 虚拟内存
		// 32位 -> 4G  
		// 64为 -> 8G
		// Text02();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

// 正确使用new和delete
class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)
		,_a2(a2)
	{}

private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	B(int b1 = 1, int b2 = 2)
		:_b1(b1)
		, _b2(b2)
	{}

private:
	int _b1 = 1;
	int _b2 = 2;
};

int main()
{
	// new 与 delete 对应
	// new[] 与 delete[] 对应
	// 串用可能会出现以下情况

	// 可能会出现报错
	A* p1 = new A[10];
	delete p1;

	B* p2 = new B[20];
	delete p2;

	return 0;
}

#endif